/**
 * 认识V8
 *  + V8是一款主流的JavaScript执行引擎
 *  + V8采用即时编译，可以直接将当前的源码翻译成机器语言执行，而之前很多JavaScript引擎都需要将源代码转成字节码，然后才去执行
 *  + V8内存设有上限，64位操作系统不超过1.5GB，32位操作系统不超过800MB（为什么设置上限：当采用增量标记算法进行垃圾回收时1.5GB只需要50ms，而采用非增量标记的方式回收需要1s）
 */

/**
 * V8垃圾回收策略
 *  + 采用分代回收的思想
 *  + 内存分为新生代、老生代
 *  + 针对不同对象采用不同算法
 *  + V8中常用GC算法：分代回收、空间复制、标记清除、标记整理、标记增量
 * 
 * V8如何回收新生代对象
 *  + V8内存分配
 *    + V8内存空间一分为二，内存左侧用于存储新生代对象，右侧用于存储老生代对象
 *    + 存储新生代的内存大小：64位系统为32MB，32位系统为16MB
 *    + 新生代指的是存活时间较短的对象（比如局部作用域中的对象，当前函数执行完后就要回收）
 * 新生代对象回收实现
 *  + 回收过程采用复制算法+标记整理
 *  + 新生代内存分为两个等大小的空间，使用空间为From，空闲空间为To
 *  + 活动对象存储于From空间
 *  + 标记整理后将活动对象拷贝至To空间
 *  + From空间进行释放，From与To交换空间并完成释放
 * 回收细节说明
 *  + 拷贝过程中可能出现晋升，某一个变量使用空间在老生代里面也会出现
 *  + 晋升就是将新生代对象移动至老生代
 *  + 一轮GC后还存活的新生代需要晋升
 *  + 活动对象拷贝到To的过程中，发现To空间的使用率超过25%，为什么是25%，因为From和To是一种交换机制，From拷贝到To后，To空间就变成了From空间，From空间随后清除后就变成了To空间，如果新的From空间使用率过大，那么新产生的新生代对象就无法存储进From空间了
 * 
 * V8如何回收老生代对象
 *  + 老生代对象说明
 *    + 老生代对象存放在右侧老生代内存区域
 *    + 存储老生代的内存大小：64位系统为1.4GB，32位系统为700MB
 *    + 老生代指的是存活时间较长的对象（比如全局作用域中的对象，或者闭包里的对象，程序关闭后才回收）
 * 老生代对象回收实现
 *  + 主要采用标记清除、标记整理、增量标记算法
 *  + 首先使用标记清除完成垃圾空间的回收，可能存在空间碎片化的情况
 *  + 当新生代区域对象要晋升到老生代时，如果正好老生代剩余空间不足以来存放新生代对象时，就会采用标记整理进行碎片空间优化
 *  + 采用增量标记进行效率优化，程序执行和垃圾回收交替运行，将标记操作拆分成很多步，而不是一次性标记完
 * 
 * 细节对比
 *  + 新生代区域由于本身存储空间较小，即使剩余一部分空间不使用也不会太浪费，所以垃圾回收采用复制算法，使用空间换时间
 *  + 老生代区域垃圾回收不适合复制算法，首先老生代空间较大，放着不用会很浪费，其次，老生代存储的数据量较大，复制所消耗的时间也比较大
 * 
 * V8总结
 *  + V8是一款主流的JavaScript执行引擎
 *  + V8内存设置上限
 *  + V8采用基于分代回收思想实现垃圾回收
 *  + V8内存分为新生代和老生代
 *  + V8垃圾回收常见的GC算法
 */

/**
 * V8引擎工作流程
 *  + 渲染引擎=>(utf-8 chunks)=>Stream(utf-16 code utils)=>Scanner扫描器(tokens)=>PreParser预解析器=>Parser解析器或者Scanner扫描器(tokens)=>Parser解析器(AST)=>Ignition解释器(bytecode)=>TurboFan编译器
 *  + V8采用即时编译，可以直接将当前的源码翻译成机器语言执行，而之前很多JavaScript引擎都需要将源代码转成字节码，然后才去执行
 *  + V8内存设有上限，64位操作系统不超过1.5GB，32位操作系统不超过800MB（为什么设置上限：当采用增量标记算法进行垃圾回收时1.5GB只需要50ms，而采用非增量标记的方式回收需要1s）
 */

// Scanner扫描器，对纯文本的js代码进行词法分析，把代码分析成不同的tokens
const username = 'tom'
// Scanner后的词法tokens
// [
//   {
//     type: 'Keyword',
//     value: 'const',
//   },
//   {
//     type: 'Identifier',
//     value: 'username',
//   },
//   {
//     type: 'Punctuator',
//     value: '=',
//   },
//   {
//     type: 'String',
//     value: 'tom',
//   },
// ]

// Parser是一个全量解析器，会把词法分析里的tokens转换成抽象的语法树（AST），解析过程中会进行语法校验，有错误会直接抛出
// {
//   type: 'Program',
//   body: [
//     {
//       type: 'VariableDeclaration',
//       declarations: [
//         {
//           type: 'VariableDeclarator',
//           id: {
//             type: 'Identifier',
//             name: 'username',
//           },
//           init: {
//             type: 'Literal',
//             value: 'tom',
//             raw: 'tom',
//           },
//         },
//       ],
//       kind: 'const',
//     },
//   ],
//   sourceType: 'script'
// }

/**
 * PreParser预解析优点
 *  + 跳过未被使用的代码
 *  + 不生成AST语法树，创建无变量引用和声明的scopes
 *  + 依据规范抛出特定错误
 *  + 解析速度更快
 */

// 只有func2被调用使用，func1只会被预解析，但是仍然会生成func1的作用域
function func1 () {
  console.log('func1')
}
function func2 () {
  console.log('func2')
}
func2()

/**
 * Parser全量解析
 *  + 解析被使用的代码
 *  + 生成AST语法树
 *  + 构建具体scopes信息，变量引用、声明等
 *  + 抛出所有语法错误
 */

// 声明时未调用，因此会被认为是不执行的代码，进行预解析
function foo (params) {
  console.log('foo')
}
// 声明时未调用，因此会被认为是不执行的代码，进行预解析
function fn () {}

// 函数立即执行，只进行一次全量解析
(function bar (params) {})()

// 执行foo，那么需要重新对foo函数进行全量解析，此时foo函数被解析了两次，所以当嵌套函数时就经常出现这种情况，应该避免过多过深的函数嵌套
foo()

/**
 * Ignition是V8提供的一个解释器，作用是将AST语法树转为字节码base code，同时收集下一个编译阶段所需要的信息（此过程也可以看作是一个预编译过程）
 */

/**
 * TurboFan是V8提供的编译器模块，会将解释器的字节码转化为具体的汇编代码，然后开始代码执行（即堆栈过程）
 */